<script>
import { GlFriendlyWrap, GlLink, GlBadge } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { REPORT_TYPES_ALL } from 'ee/security_dashboard/store/constants';
import FalsePositiveAlert from 'ee/vulnerabilities/components/false_positive_alert.vue';
import GenericReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue';
import {
  SUPPORTING_MESSAGE_TYPES,
  VULNERABILITY_TRAINING_HEADING,
} from 'ee/vulnerabilities/constants';
import {
  convertObjectPropsToCamelCase,
  convertArrayOfObjectsToCamelCase,
} from '~/lib/utils/common_utils';
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import VulnerabilityTraining from 'ee/vulnerabilities/components/vulnerability_training.vue';
import getFileLocation from '../store/utils/get_file_location';
import { bodyWithFallBack } from './helpers';
import SeverityBadge from './severity_badge.vue';
import VulnerabilityDetail from './vulnerability_detail.vue';

export default {
  name: 'VulnerabilityDetails',
  components: {
    CodeBlock,
    GenericReportSection,
    GlFriendlyWrap,
    SeverityBadge,
    VulnerabilityDetail,
    GlLink,
    GlBadge,
    FalsePositiveAlert,
    VulnerabilityTraining,
  },
  directives: {
    SafeHtml,
  },
  props: { vulnerability: { type: Object, required: true } },
  data() {
    return {
      showTraining: false,
    };
  },
  computed: {
    url() {
      return this.vulnerability.request?.url || getFileLocation(this.vulnLocation);
    },
    file() {
      const file = this.vulnerability?.location?.file;
      if (!file) {
        return null;
      }
      let lineSuffix = '';
      const { start_line: startLine, end_line: endLine } = this.vulnLocation;
      if (startLine) {
        lineSuffix += `:${startLine}`;
        if (endLine && startLine !== endLine) {
          lineSuffix += `-${endLine}`;
        }
      }
      return `${file}${lineSuffix}`;
    },
    identifiers() {
      return this.asNonEmptyListOrNull(this.vulnerability.identifiers);
    },
    vulnLocation() {
      return this.vulnerability.location;
    },
    crashAddress() {
      return this.vulnLocation?.crash_address;
    },
    crashType() {
      return this.vulnLocation?.crash_type;
    },
    crashState() {
      return this.vulnLocation?.crash_state;
    },
    className() {
      return this.vulnLocation?.class;
    },
    methodName() {
      return this.vulnLocation?.method;
    },
    image() {
      return this.vulnLocation?.image;
    },
    namespace() {
      return this.vulnLocation?.operating_system;
    },
    assets() {
      return this.asNonEmptyListOrNull(this.vulnerability.assets);
    },
    links() {
      return this.asNonEmptyListOrNull(this.vulnerability.links);
    },
    assertion() {
      return this.vulnerability.evidenceSource?.name;
    },
    recordedMessage() {
      return this.vulnerability.supporting_messages?.find(
        (msg) => msg.name === SUPPORTING_MESSAGE_TYPES.RECORDED,
      )?.response;
    },
    constructedRequest() {
      const { request } = this.vulnerability;
      return request ? this.constructRequest(request) : '';
    },
    constructedResponse() {
      const { response } = this.vulnerability;
      return response ? this.constructResponse(response) : '';
    },
    constructedRecordedResponse() {
      return this.recordedMessage ? this.constructResponse(this.recordedMessage) : '';
    },
    responseStatusCode() {
      return this.vulnerability.response?.status_code;
    },
    scannerType() {
      return REPORT_TYPES_ALL[this.vulnerability.report_type];
    },
    scannerUrl() {
      return this.vulnerability.scanner?.url || '';
    },
    scannerVersion() {
      return this.vulnerability.scanner?.version;
    },
    scannerName() {
      return this.vulnerability.scanner?.name;
    },
    scannerProvider() {
      if (!this.scannerVersion) {
        return this.scannerName;
      }
      return sprintf(s__('Vulnerability|%{scannerName} (version %{scannerVersion})'), {
        scannerName: this.scannerName,
        scannerVersion: this.scannerVersion,
      });
    },
    stacktraceSnippet() {
      return this.vulnLocation?.stacktrace_snippet;
    },
    falsePositive() {
      return this.vulnerability.false_positive;
    },
    hasDescription() {
      return this.vulnerability.description_html || this.vulnerability.description;
    },
    hasRequest() {
      return Boolean(this.constructedRequest);
    },
    hasResponse() {
      return Boolean(this.constructedResponse);
    },
    hasRecordedResponse() {
      return Boolean(this.constructedRecordedResponse);
    },
    normalizedProjectFullPath() {
      const projectFullPath = this.vulnerability.project?.full_path;

      return projectFullPath ? cleanLeadingSeparator(projectFullPath) : '';
    },
    camelCaseFormattedIdentifiers() {
      return convertArrayOfObjectsToCamelCase(this.identifiers);
    },
  },
  methods: {
    getHeadersAsCodeBlockLines(headers) {
      return Array.isArray(headers)
        ? headers.map(({ name, value }) => `${name}: ${value}`).join('\n')
        : '';
    },
    hasMoreValues(index, values) {
      return index < values.length - 1;
    },
    asNonEmptyListOrNull(list) {
      return list?.length > 0 ? list : null;
    },
    constructResponse(response) {
      const { body, statusCode, reasonPhrase = '', headers = [] } = convertObjectPropsToCamelCase(
        response,
      );
      const headerLines = this.getHeadersAsCodeBlockLines(headers);

      return statusCode && headerLines
        ? [`${statusCode} ${reasonPhrase}\n`, headerLines, '\n\n', bodyWithFallBack(body)].join('')
        : '';
    },
    constructRequest(request) {
      const { body, method, url, headers = [] } = request;
      const headerLines = this.getHeadersAsCodeBlockLines(headers);

      return method && url && headerLines
        ? [`${method} ${url}\n`, headerLines, '\n\n', bodyWithFallBack(body)].join('')
        : '';
    },
    handleShowTraining(showVulnerabilityTraining) {
      this.showTraining = showVulnerabilityTraining;
    },
  },
  i18n: {
    VULNERABILITY_TRAINING_HEADING,
  },
};
</script>
<template>
  <div class="border-white mb-0 px-3">
    <false-positive-alert v-if="falsePositive" />
    <vulnerability-detail v-if="vulnerability.state" :label="s__('Vulnerability|Status')">
      <gl-badge variant="warning" class="text-capitalize">{{ vulnerability.state }}</gl-badge>
    </vulnerability-detail>
    <vulnerability-detail v-if="hasDescription" :label="s__('Vulnerability|Description')">
      <p
        v-if="vulnerability.description_html"
        v-safe-html="vulnerability.description_html"
        data-testid="description"
      ></p>
      <p v-else data-testid="description">{{ vulnerability.description }}</p>
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.project" :label="s__('Vulnerability|Project')">
      <gl-link ref="projectLink" :href="vulnerability.project.full_path" target="_blank">
        <gl-friendly-wrap :text="vulnerability.project.full_name" />
      </gl-link>
    </vulnerability-detail>
    <vulnerability-detail v-if="methodName" :label="s__('Vulnerability|Method')">
      <gl-friendly-wrap :text="methodName" />
    </vulnerability-detail>
    <vulnerability-detail v-if="url" :label="__('URL')">
      <gl-link ref="urlLink" :href="url" target="_blank">
        <gl-friendly-wrap :text="url" />
      </gl-link>
    </vulnerability-detail>
    <vulnerability-detail v-if="hasRequest" :label="s__('Vulnerability|Request')">
      <code-block ref="request" :code="constructedRequest" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail
      v-if="hasRecordedResponse"
      :label="s__('Vulnerability|Unmodified Response')"
    >
      <code-block ref="recordedResponse" :code="constructedRecordedResponse" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="hasResponse" :label="s__('Vulnerability|Actual Response')">
      <code-block ref="unmodifiedResponse" :code="constructedResponse" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="file" :label="s__('Vulnerability|File')">
      <gl-link
        v-if="vulnerability.blob_path"
        ref="fileLink"
        :href="vulnerability.blob_path"
        target="_blank"
      >
        <gl-friendly-wrap :text="file" />
      </gl-link>
      <gl-friendly-wrap v-else :text="file" />
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.evidence" :label="s__('Vulnerability|Evidence')">
      <gl-friendly-wrap :text="vulnerability.evidence" />
    </vulnerability-detail>
    <vulnerability-detail v-if="crashAddress" :label="s__('Vulnerability|Crash address')">
      <span ref="crashAddress"> {{ crashAddress }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="crashType" :label="s__('Vulnerability|Crash type')">
      <span ref="crashType"> {{ crashType }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="crashState" :label="s__('Vulnerability|Crash state')">
      <span ref="crashState"> {{ crashState }} </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="stacktraceSnippet" :label="__('Stacktrace snippet')">
      <code-block ref="stacktraceSnippet" :code="stacktraceSnippet" max-height="225px" />
    </vulnerability-detail>
    <vulnerability-detail v-if="identifiers" :label="s__('Vulnerability|Identifiers')">
      <span v-for="(identifier, i) in identifiers" :key="i">
        <gl-link
          v-if="identifier.url"
          ref="identifiersLink"
          class="gl-word-break-all"
          :href="identifier.url"
          target="_blank"
          rel="nofollow"
        >
          {{ identifier.name }}
        </gl-link>
        <span v-else> {{ identifier.name }} </span>
        <span v-if="hasMoreValues(i, identifiers)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.severity" :label="s__('Vulnerability|Severity')">
      <severity-badge :severity="vulnerability.severity" class="d-inline" />
    </vulnerability-detail>
    <vulnerability-detail v-if="vulnerability.report_type" :label="s__('Vulnerability|Tool')">
      <gl-friendly-wrap :text="scannerType" />
    </vulnerability-detail>
    <vulnerability-detail v-if="scannerProvider" :label="s__('Vulnerability|Scanner Provider')">
      <gl-link v-if="scannerUrl" ref="scannerLink" :href="scannerUrl" target="_blank">
        <gl-friendly-wrap :text="scannerProvider" />
      </gl-link>
      <gl-friendly-wrap v-else :text="scannerProvider" />
    </vulnerability-detail>
    <vulnerability-detail v-if="className" :label="s__('Vulnerability|Class')">
      <gl-friendly-wrap :text="className" />
    </vulnerability-detail>
    <vulnerability-detail v-if="image" :label="s__('Vulnerability|Image')">
      <gl-friendly-wrap :text="image" />
    </vulnerability-detail>
    <vulnerability-detail v-if="namespace" :label="s__('Vulnerability|Namespace')">
      <gl-friendly-wrap :text="namespace" />
    </vulnerability-detail>
    <vulnerability-detail v-if="links" :label="s__('Vulnerability|Links')">
      <span v-for="(link, i) in links" :key="i">
        <gl-link ref="linksLink" class="gl-word-break-all" :href="link.url" target="_blank">
          {{ link.value || link.url }}
        </gl-link>
        <span v-if="hasMoreValues(i, links)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
    <vulnerability-detail v-if="assets" :label="s__('Vulnerability|Reproduction Assets')">
      <span v-for="(asset, i) in assets" :key="i">
        <gl-link
          v-if="asset.url"
          ref="assetsLink"
          class="gl-word-break-all"
          :href="asset.url"
          target="_blank"
          rel="nofollow"
        >
          {{ s__('Vulnerability|Download') }} {{ asset.name }}
        </gl-link>
        <span v-else> {{ asset.name }} </span>
        <span v-if="hasMoreValues(i, assets)">,&nbsp;</span>
      </span>
    </vulnerability-detail>
    <generic-report-section
      v-if="vulnerability.details"
      class="gl-mt-4"
      :details="vulnerability.details"
    />
    <div v-if="identifiers && normalizedProjectFullPath" v-show="showTraining">
      <vulnerability-detail :label="$options.i18n.VULNERABILITY_TRAINING_HEADING.title">
        <vulnerability-training
          :project-full-path="normalizedProjectFullPath"
          :identifiers="camelCaseFormattedIdentifiers"
          :file="vulnLocation.file"
          @show-vulnerability-training="handleShowTraining"
        />
      </vulnerability-detail>
    </div>
  </div>
</template>
